package com.jiyongjun.component.core.impl;

import com.jiyongjun.component.annotation.ApiMethodMetadata;
import com.jiyongjun.component.config.LogBizConfig;
import com.jiyongjun.component.config.LogSwitch;
import com.jiyongjun.component.core.model.DubboLogContext;
import com.jiyongjun.component.enums.LogApiTypeEnum;
import com.jiyongjun.component.enums.LogResultTypeEnum;
import com.jiyongjun.component.exception.BizException;
import com.jiyongjun.component.exception.convertor.DubboProviderExceptionConvertor;
import com.jiyongjun.component.exception.parser.ExceptionParseResult;
import com.jiyongjun.component.exception.parser.ExceptionParser;
import com.jiyongjun.component.utils.JsonUtils;
import com.jiyongjun.component.utils.LogContextUtils;
import com.jiyongjun.component.utils.ParamValidator;
import com.jiyongjun.component.utils.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.rpc.AppResponse;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

import static com.jiyongjun.component.enums.LogConstant.LOG_EXCEPTION_KEYWORD;

/**
 * <h2></h2>
 * <h3>描述：处理流程</h3>
 * <ol>
 *     <li>{@link DubboWatchDogServerTemplate#onDubboEnter}</li>
 *     <li>{@link DubboWatchDogServerTemplate#onHandleApiMethodMetadata}</li>
 *     <li>{@link DubboWatchDogServerTemplate#onDubboResponse}</li>
 *     <ol>
 *        <li>{@link DubboWatchDogServerTemplate#onParseException}</li>
 *        <li>{@link DubboWatchDogServerTemplate#onLogException}</li>
 *        <li>{@link DubboWatchDogServerTemplate#onConvertExceptionToResult}</li>
 *        <li>{@link DubboWatchDogServerTemplate#onLogHsfResponseWithoutException}</li>
 *     </ol>
 *
 * </ol>
 *
 * @author yongjun.jyj
 * @since 2022/7/14 20:27
 */
@Slf4j
public class DubboWatchDogServerTemplate {
    @Autowired
    private List<ExceptionParser> exceptionParsers;
    @Autowired
    private DubboProviderExceptionConvertor dubboProviderExceptionConvertor;

    public DubboLogContext onDubboEnter(Invocation invocation) {
        String apiUniqueName = LogContextUtils.generateServerApiUniqueName(invocation);
        DubboLogContext existLogContext = DubboLogContext.getLogContext(apiUniqueName, false);
        if (existLogContext != null) {
            log.error("{}|日志上下文未自动关闭|apiUniqueName={}", LOG_EXCEPTION_KEYWORD, apiUniqueName);
            existLogContext.drop();
        }
        return DubboLogContext.getLogContext(apiUniqueName, true)
                .setMethod(getMethod(invocation))
                .setApiName(LogContextUtils.generateServerApiName(invocation))
                .setArgs(invocation.getArguments());
    }

    private static Method getMethod(Invocation invocation) {
        return invocation.getServiceModel().getServiceModel().getMethod(invocation.getMethodName(), invocation.getParameterTypes()).getMethod();
    }

    private ApiMethodMetadata getApiMethodMetadata(Invocation invocation) {
        return LogContextUtils.getApiMethodMetadata(invocation);
    }

    public Result onHandleApiMethodMetadata(Invocation invocation, DubboLogContext logContext) {
        ApiMethodMetadata methodMetadata = getApiMethodMetadata(invocation);
        try {
            String apiType = judgeApiType(methodMetadata, invocation);
            logContext.setApiType(apiType);
            if (StringUtils.isNotBlank(methodMetadata.desc())) {
                logContext.setApiDesc(methodMetadata.desc());
            }
            if (StringUtils.isNotBlank(methodMetadata.ext())) {
                logContext.setExt(methodMetadata.ext());
            }
            if (methodMetadata.logArgs()) {
                logContext.setArgs(invocation.getArguments());
            }
            if (methodMetadata.autoValidateArgs() && invocation.getArguments() != null) {
                Boolean validateArgsWithoutValidAnnotation = SpringContextHolder.getBeanWithCache(LogBizConfig.class).getValidateArgsWithoutValidAnnotation();
                try {
                    if (validateArgsWithoutValidAnnotation) {
                        for (Object arg : invocation.getArguments()) {
                            if (arg == null) {
                                continue;
                            }
                            ParamValidator.validate(arg);
                        }
                    } else {
                        Object proxyObject = invocation.getServiceModel().getProxyObject();
                        Method method = getMethod(invocation);
                        ParamValidator.validateParameters(proxyObject, method, invocation.getArguments(), methodMetadata.argGroup());
                    }
                } catch (BizException e) {
                    return new AppResponse(e);
                }
            }
        } catch (Throwable throwable) {
            log.warn("{}|记录hsf日志异常|msg={}", LOG_EXCEPTION_KEYWORD, throwable.getMessage(), throwable);
        }
        
        return null;
    }

    protected String judgeApiType(ApiMethodMetadata methodMetadata, Invocation invocation) {
        if (methodMetadata.apiType() != null && !methodMetadata.apiType().isBlank()) {
            return methodMetadata.apiType();
        }
        
        // todo 判断其他类型
        return LogApiTypeEnum.DUBBO_PROVIDER.getCode();
    }

    public void onDubboResponse(Invocation invocation, Result rpcResult) {
        String apiName = LogContextUtils.generateServerApiUniqueName(invocation);

        DubboLogContext logContext = DubboLogContext.getLogContext(apiName, false);
        if (logContext == null) {
            log.error("{}|根据apiName未获取到日志上下文|apiName={}", LOG_EXCEPTION_KEYWORD, apiName);
            logContext = DubboLogContext.getLogContext(apiName, true);
        }

        try {
            // 解析异常
            ExceptionParseResult exceptionParseResult = onParseException(invocation, rpcResult);
            if (exceptionParseResult != null) {
                // 记录异常
                onLogException(exceptionParseResult, logContext);
                // 转换异常为自定义Result
                onConvertExceptionToResult(invocation, rpcResult, exceptionParseResult, logContext);
            } else {
                // 记录结果
                onLogHsfResponseWithoutException(invocation, rpcResult, logContext);
            }
        } catch (Exception e) {
            log.error("{}|记录dubbo日志异常|msg={}", LOG_EXCEPTION_KEYWORD, e.getMessage(), e);
        } finally {
            logContext.close();
        }

    }

    protected ExceptionParseResult onParseException(Invocation invocation, Result rpcResult) {
        Object appResponse = rpcResult.getValue();
        if (!(appResponse instanceof Throwable)) {
            return null;
        }

        Throwable throwable = (Throwable) appResponse;
        return exceptionParsers.stream()
                .sorted(Comparator.comparingInt(Ordered::getOrder))
                .filter(exceptionHandler -> {
                    try {
                        return exceptionHandler.isMatch(throwable);
                    } catch (Throwable e) {
                        return false;
                    }
                })
                .findFirst()
                .map(exceptionParser -> exceptionParser.handle(invocation, throwable))
                .orElse(null);
    }

    protected void onLogException(ExceptionParseResult exceptionParseResult, DubboLogContext logContext) {
        if (exceptionParseResult != null) {
            String exceptionType = exceptionParseResult.getExceptionType();
            if (LogResultTypeEnum.BIZ_EXCEPTION.getCode().equals(exceptionType) || LogSwitch.BIZ_EXCEPTION_CODES.contains(exceptionParseResult.getMsgCode())) {
                logContext.setBizException(exceptionParseResult.getMsgCode(), exceptionParseResult.getMsgInfo(), exceptionParseResult.getRaw())
                        .setErrorPayload(JsonUtils.toJson(exceptionParseResult.getPayload()));
            } else {
                logContext.setServiceException(exceptionParseResult.getMsgCode(), exceptionParseResult.getMsgInfo(), exceptionParseResult.getRaw())
                        .setErrorPayload(JsonUtils.toJson(exceptionParseResult.getPayload()));
            }
        }
    }

    private void onConvertExceptionToResult(Invocation invocation, Result rpcResult, ExceptionParseResult exceptionParseResult, DubboLogContext logContext) {
        Method method = getMethod(invocation);
        Object result = dubboProviderExceptionConvertor.converExceptionToReturnTypeInstance(method.getReturnType(), exceptionParseResult);
        if (result != null) {
            rpcResult.setValue(result);
        }

        ApiMethodMetadata methodMetadata = getApiMethodMetadata(invocation);
        if (methodMetadata.logResult() && !LogSwitch.LOG_RESULT_API_BLACKLIST.contains(logContext.getApiUniqueName())) {
            Object appResponse = rpcResult.getValue();

            if (appResponse instanceof  Throwable) {
                // 异常堆栈已经记录了详细信息，这里只需要简单记录即可
                HashMap<String, String> map = new HashMap<>();
                map.put("type", appResponse.getClass().getName());
                map.put("message", ((Throwable) appResponse).getMessage());
            } else {
                logContext.setResult(appResponse);
            }
        }
    }

    private void onLogHsfResponseWithoutException(Invocation invocation, Result rpcResult, DubboLogContext logContext) {
        ApiMethodMetadata methodMetadata = getApiMethodMetadata(invocation);
        if (methodMetadata.logResult() && !LogSwitch.LOG_RESULT_API_BLACKLIST.contains(logContext.getApiUniqueName())) {
            logContext.setResult(rpcResult.getValue());
        }

        dubboProviderExceptionConvertor.handleResponse(rpcResult, logContext);
    }

}
